name: Conformance Gateway API (ci-gateway-api)

# Any change in triggers needs to be reflected in the concurrency group.
on:
  workflow_dispatch:
    inputs:
      PR-number:
        description: "Pull request number."
        required: true
      context-ref:
        description: "Context in which the workflow runs. If PR is from a fork, will be the PR target branch (general case). If PR is NOT from a fork, will be the PR branch itself (this allows committers to test changes to workflows directly from PRs)."
        required: true
      SHA:
        description: "SHA under test (head of the PR branch)."
        required: true
      extra-args:
        description: "[JSON object] Arbitrary arguments passed from the trigger comment via regex capture group. Parse with 'fromJson(inputs.extra-args).argName' in workflow."
        required: false
        default: '{}'

  push:
    branches:
      - main
      - ft/main/**
    paths-ignore:
      - 'Documentation/**'
      - 'test/**'

# By specifying the access of one of the scopes, all of those that are not
# specified are set to 'none'.
permissions:
  # To be able to access the repository with actions/checkout
  contents: read
  # To allow retrieving information from the PR API
  pull-requests: read
  # To be able to set commit status
  statuses: write

concurrency:
  # Structure:
  # - Workflow name
  # - Event type
  # - A unique identifier depending on event type:
  #   - schedule: SHA
  #   - workflow_dispatch: PR number
  #
  # This structure ensures a unique concurrency group name is generated for each
  # type of testing, such that re-runs will cancel the previous run.
  group: |
    ${{ github.workflow }}
    ${{ github.event_name }}
    ${{
      (github.event_name == 'push' && github.sha) ||
      (github.event_name == 'workflow_dispatch' && github.event.inputs.PR-number)
    }}
  cancel-in-progress: true

env:
  cilium_cli_ci_version:
  CILIUM_CLI_MODE: helm
  # renovate: datasource=github-releases depName=kubernetes-sigs/kind
  kind_version: v0.22.0
  kind_config: .github/kind-config.yaml
  gateway_api_version: v1.0.0
  timeout: 5m

jobs:
  commit-status-start:
    if: ${{ github.event_name != 'push' }}
    name: Commit Status Start
    runs-on: ubuntu-latest
    steps:
      - name: Set initial commit status
        uses: myrotvorets/set-commit-status-action@38f3f27c7d52fb381273e95542f07f0fba301307 # v2.0.0
        with:
          sha: ${{ inputs.SHA || github.sha }}

  gateway-api-conformance-test:
    name: Gateway API Conformance Test
    runs-on: ubuntu-latest
    timeout-minutes: 120
    strategy:
      fail-fast: false
      matrix:
        include:
        - crd-channel: experimental
          conformance-profile: false
        - crd-channel: standard
          conformance-profile: false
        - crd-channel: experimental
          conformance-profile: true
    steps:
      - name: Checkout context ref (trusted)
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
        with:
          ref: ${{ inputs.context-ref || github.sha }}
          persist-credentials: false

      - name: Set Environment Variables
        uses: ./.github/actions/set-env-variables

      - name: Install Cilium CLI
        uses: cilium/cilium-cli@7306e3cdc6caee738157f08e3e1ba26179f104e5 # v0.15.23
        with:
          repository: ${{ env.CILIUM_CLI_RELEASE_REPO }}
          release-version: ${{ env.CILIUM_CLI_VERSION }}
          ci-version: ${{ env.cilium_cli_ci_version }}

      - name: Get Cilium's default values
        id: default_vars
        uses: ./.github/actions/helm-default
        with:
          image-tag: ${{ inputs.SHA }}
          chart-dir: ./untrusted/install/kubernetes/cilium

      - name: Set image tag
        id: vars
        run: |
          echo sha=${{ steps.default_vars.outputs.sha }} >> $GITHUB_OUTPUT
          
          EXEMPT_FEATURES="GatewayPort8080,GatewayStaticAddresses,Mesh"
          if [ ${{ matrix.crd-channel }} == "standard" ]; then
            EXEMPT_FEATURES+=",HTTPRouteParentRefPort,HTTPRouteDestinationPortMatching,HTTPRouteRequestTimeout,HTTPRouteBackendTimeout"
          fi

          CILIUM_INSTALL_DEFAULTS="${{ steps.default_vars.outputs.cilium_install_defaults }} \
            --helm-set=debug.verbose=envoy \
            --helm-set kubeProxyReplacement=true \
            --helm-set=gatewayAPI.enabled=true \
            --helm-set=l2announcements.enabled=true \
            --helm-set=devices='{eth0}'"

          echo cilium_install_defaults=${CILIUM_INSTALL_DEFAULTS} >> $GITHUB_OUTPUT
          echo skipped_tests=${SKIPPED_TESTS} >> $GITHUB_OUTPUT
          echo exempt-features=${EXEMPT_FEATURES} >> $GITHUB_OUTPUT

      # Warning: since this is a privileged workflow, subsequent workflow job
      # steps must take care not to execute untrusted code.
      - name: Checkout pull request branch (NOT TRUSTED)
        uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
        with:
          ref: ${{ steps.vars.outputs.sha }}
          persist-credentials: false
          path: untrusted
          sparse-checkout: |
            install/kubernetes/cilium
            examples

      - name: Create kind cluster
        uses: helm/kind-action@99576bfa6ddf9a8e612d83b513da5a75875caced # v1.9.0
        with:
          version: ${{ env.kind_version }}
          config: ${{ env.kind_config }}

      - name: Install Go
        uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0
        with:
          # renovate: datasource=golang-version depName=go
          go-version: 1.22.0

      - name: Wait for images to be available
        timeout-minutes: 30
        shell: bash
        run: |
          for image in cilium-ci operator-generic-ci ; do
            until docker manifest inspect quay.io/${{ env.QUAY_ORGANIZATION_DEV }}/$image:${{ steps.vars.outputs.sha }} &> /dev/null; do sleep 45s; done
          done

      - name: Install Gateway API CRDs
        run: |
          # Install Gateway CRDs
          kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${{ env.gateway_api_version }}/config/crd/${{ matrix.crd-channel }}/gateway.networking.k8s.io_gatewayclasses.yaml
          kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${{ env.gateway_api_version }}/config/crd/${{ matrix.crd-channel }}/gateway.networking.k8s.io_gateways.yaml
          kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${{ env.gateway_api_version }}/config/crd/${{ matrix.crd-channel }}/gateway.networking.k8s.io_httproutes.yaml
          kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${{ env.gateway_api_version }}/config/crd/${{ matrix.crd-channel }}/gateway.networking.k8s.io_referencegrants.yaml
          ## TLSRoute is only available in experimental channel in v0.7.0
          kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${{ env.gateway_api_version }}/config/crd/experimental/gateway.networking.k8s.io_tlsroutes.yaml
          kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/gateway-api/${{ env.gateway_api_version }}/config/crd/experimental/gateway.networking.k8s.io_grpcroutes.yaml

          # To make sure that Gateway API CRs are available
          kubectl wait --for condition=Established crd/gatewayclasses.gateway.networking.k8s.io --timeout=${{ env.timeout }}
          kubectl wait --for condition=Established crd/gateways.gateway.networking.k8s.io --timeout=${{ env.timeout }}
          kubectl wait --for condition=Established crd/httproutes.gateway.networking.k8s.io --timeout=${{ env.timeout }}
          kubectl wait --for condition=Established crd/tlsroutes.gateway.networking.k8s.io --timeout=${{ env.timeout }}
          kubectl wait --for condition=Established crd/grpcroutes.gateway.networking.k8s.io --timeout=${{ env.timeout }}
          kubectl wait --for condition=Established crd/referencegrants.gateway.networking.k8s.io --timeout=${{ env.timeout }}

      - name: Install Cilium
        id: install-cilium
        run: |
          cilium install ${{ steps.vars.outputs.cilium_install_defaults }}

      - name: Wait for Cilium status to be ready
        run: |
          cilium status --wait
          kubectl -n kube-system get pods

      - name: Install Cilium LB IPPool and L2 Announcement Policy
        timeout-minutes: 10
        run: |
          KIND_NET_CIDR=$(docker network inspect kind -f '{{(index .IPAM.Config 0).Subnet}}')
          LB_CIDR=$(echo ${KIND_NET_CIDR} | sed "s@0.0/16@255.200/28@")

          echo "Deploying LB-IPAM Pool..."
          cat << EOF > pool.yaml
          apiVersion: "cilium.io/v2alpha1"
          kind: CiliumLoadBalancerIPPool
          metadata:
            name: "pool"
          spec:
            cidrs:
              - cidr: "$LB_CIDR"
          EOF
          kubectl apply -f pool.yaml
          
          echo "Deploying L2-Announcement Policy..."
          cat << 'EOF' > l2policy.yaml
          apiVersion: "cilium.io/v2alpha1"
          kind: CiliumL2AnnouncementPolicy
          metadata:
            name: l2policy
          spec:
            loadBalancerIPs: true
            interfaces:
              - eth0
            nodeSelector:
              matchExpressions:
                - key: node-role.kubernetes.io/control-plane
                  operator: DoesNotExist
          EOF
          kubectl apply -f l2policy.yaml

      - name: Run simple Gateway API GRPCRoute test (temporary till upstream conformance tests)
        timeout-minutes: 10
        run: |
          kubectl apply -f untrusted/examples/kubernetes/gateway/grpc-route.yaml
          # Install grpcurl binary
          go install github.com/fullstorydev/grpcurl/cmd/grpcurl@latest
          
          # Wait for the deployment
          kubectl wait --for=condition=Available --all deployment --timeout=${{ env.timeout }}
          lb=$(kubectl get services cilium-gateway-grpc -o json | jq '.status.loadBalancer.ingress[0].ip' | jq -r .)
          grpcurl -plaintext -authority=my-grpc-service.foo.com $lb:80 yages.Echo/Ping
          curl -s -v --fail $lb/yages.Echo/Ping \
            -XPOST \
            -H 'Host: my-grpc-service.foo.com' \
            -H 'Content-Type: application/grpc-web-text' \
            -H 'Accept: application/grpc-web-text' \
            -d'AAAAAAA='

      - name: Run Gateway API conformance test
        timeout-minutes: 30
        run: |
          if [ ${{ matrix.conformance-profile }} == "true" ]; then
            GATEWAY_API_CONFORMANCE_TESTS=1 go test \
              -p 4 \
              -v ./operator/pkg/gateway-api \
              --gateway-class cilium \
              --all-features \
              --exempt-features "${{ steps.vars.outputs.exempt-features }}" \
              --conformance-profiles HTTP,TLS \
              --organization cilium \
              --project cilium \
              --url github.com/cilium/cilium \
              --version main \
              --contact https://github.com/cilium/community/blob/main/roles/Maintainers.md \
              --report-output report.yaml \
              -test.run "TestExperimentalConformance" \
              -test.skip "${{ steps.vars.outputs.skipped_tests }}"
          else
            GATEWAY_API_CONFORMANCE_TESTS=1 go test \
              -p 4 \
              -v ./operator/pkg/gateway-api \
              --gateway-class cilium \
              --all-features \
              --exempt-features "${{ steps.vars.outputs.exempt-features }}" \
              -test.run "TestConformance" \
              -test.skip "${{ steps.vars.outputs.skipped_tests }}"
          fi

      - name: Upload report artifacts
        uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
        with:
          name: report-${{ matrix.conformance-profile }}-${{ matrix.crd-channel }}.yaml
          path: operator/pkg/gateway-api/report.yaml
          retention-days: 5
          if-no-files-found: ignore

      - name: Post-test information gathering
        if: ${{ !success() && steps.install-cilium.outcome != 'skipped' }}
        run: |
          kubectl get pods --all-namespaces -o wide
          cilium status
          cilium sysdump --output-filename cilium-sysdump-out-${{ join(matrix.*, '-') }}
        shell: bash {0} # Disable default fail-fast behaviour so that all commands run independently

      - name: Upload artifacts
        if: ${{ !success() }}
        uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
        with:
          name: cilium-sysdump-out-${{ matrix.conformance-profile }}-${{ matrix.crd-channel }}
          path: cilium-sysdump-out-*.zip
          retention-days: 5

  commit-status-final:
    if: ${{ always() && github.event_name != 'push' }}
    name: Commit Status Final
    needs: gateway-api-conformance-test
    runs-on: ubuntu-latest
    steps:
      - name: Set final commit status
        uses: myrotvorets/set-commit-status-action@38f3f27c7d52fb381273e95542f07f0fba301307 # v2.0.0  
        with:
          sha: ${{ inputs.SHA || github.sha }}
          status: ${{ needs.gateway-api-conformance-test.result }}
